<?php
/**
 * 
 * Class for representing an SQL table.
 * 
 * @category Abovo
 * 
 * @package Abovo_Sql
 * 
 * @author Antti Holvikari <anttih@gmail.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id$
 * 
 */

/**
 * 
 * Class for representing an SQL table.
 * 
 * @category Abovo
 * 
 * @package Abovo_Sql_Table
 * 
 */
class Abovo_Sql_Table extends Solar_Base {

    /**
     * 
     * User-provided configuration.
     * 
     * Keys are ...
     * 
     * `sql`
     * : (dependency) A Solar_Sql dependency object.
     * 
     * `paging`
     * : (int) Number of rows per page.
     * 
     * `create`
     * : (bool) Attempt to auto-create the table?
     * 
     * `autoconnect`
     * : (bool) Whether to attempt to create the table at construction
     * 
     * @var array
     * 
     */
    protected $_Abovo_Sql_Table = array(
        'sql'         => 'sql',
        'paging'      => 10,
        'create'      => true,
        'autoconnect' => true,
    );
    
    /**
     * 
     * The table name.
     * 
     * @var string
     * 
     */
    protected $_name = null;
    
    /**
     * 
     * The default order when fetching rows.
     * 
     * @var array
     * 
     */
    protected $_order = array('id');
    
    /**
     * 
     * The numer of rows per page when selecting.
     * 
     * @var int
     * 
     */
    protected $_paging = 10;
    
    /**
     * 
     * Have we connected to the database?
     * 
     * @var bool
     * 
     */
    protected $_connected = false;
    
    /**
     * 
     * The column specification array for all columns in this table.
     * 
     * Each element in this array looks like this...
     * 
     * {{code: php
     *     $col = array(
     *         'col_name' => array(
     *             'name'    => (string) the col_name, same as the key
     *             'type'    => (string) char, varchar, date, etc
     *             'size'    => (int) column size
     *             'scope'   => (int) decimal places
     *             'valid'   => (array) Solar_Valid methods and args
     *             'require' => (bool) is this a required (non-null) column?
     *             'autoinc' => (bool) auto-increment
     *             'default' => (string|array) default value
     *             'primary' => (bool) is this part of the primary key?
     *          ),
     *     );
     * }}
     * 
     * @var array
     * 
     */
    protected $_col = array();
    
    /**
     * 
     * The index specification array for all indexes on this table.
     * 
     * The array should be in this format ...
     * 
     * {{code: php
     *     // the index type: 'normal' or 'unique'
     *     $type = 'normal';
     *     
     *     // index on a single column:
     *     // CREATE INDEX idx_name ON table_name (col_name)
     *     $this->_idx['idx_name'] = array($type, 'col_name');
     * 
     *     // index on multiple columns:
     *     // CREATE INDEX idx_name ON table_name (col1, col2, ... colN)
     *     $this->_idx['idx_name'] = array(
     *         $type,
     *         array('col1', 'col2', ..., 'colN')
     *     );
     *     
     *     // easy shorthand for an index on a single column,
     *     // giving the index the same name as the column:
     *     // CREATE INDEX col_name ON table_name (col_name)
     *     $this->_idx['col_name'] = $type; 
     * }}
     * 
     * The $type may be 'normal' or 'unique'.
     * 
     * @var array
     * 
     * @see addIndex()
     * 
     */
    protected $_idx = array();
    
    /**
     * 
     * A Solar_Sql dependency object.
     * 
     * @var Solar_Sql
     * 
     */
    protected $_sql = null;
    
    /**
     * 
     * Column name which will be used for create timestamp
     * 
     * @var string
     * 
     */
    protected $_col_created = 'created';
    
    /**
     * 
     * Column name which will be used for update timestamp
     * 
     * @var string
     * 
     */
    protected $_col_updated = 'updated';
    
    /**
     * 
     * The object class returned by fetch(), fetchNew(), and fetchRow().
     * 
     * @var string
     * 
     */
    protected $_row_class = 'Solar_Sql_Row';
    
    /**
     * 
     * The object class returned by fetchAll().
     * 
     * @var string
     * 
     */
    protected $_all_class = 'Solar_Sql_Rowset';
    
    /**
     * 
     * DataFilter class to use
     * 
     * @var string
     * 
     */
    protected $_filter_class = 'Abovo_DataFilter';
    
    /**
     * 
     * Constructor.
     * 
     * @param array $config User-provided configuration values.
     * 
     * @return void
     * 
     */
    public function __construct($config = null)
    {
        // main construction
        parent::__construct($config);
        $this->setPaging($this->_config['paging']);
        
        // perform column and index setup, then fix everything.
        $this->_setup();
        $this->_autoSetup();
        
        // instantiate datafilter class
        $this->_obj_filter = Solar::factory($this->_filter_class);
        
        // autoconnect and create tables
        if ($this->_config['autoconnect'] !== false) {
            $this->_connect();
        }
    }
    
    /**
     * 
     * Connects to the database and auto-creates the table if requested.
     * 
     * @return void
     * 
     */
    protected function _connect()
    {
        if (! $this->_connected) {
            $this->_sql = Solar::dependency('Solar_Sql', $this->_config['sql']);
            
            if ($this->_config['create']) {
                $this->_autoCreate();
            }
            
            $this->_connected = true;
        }
    }
    
    /**
     * 
     * Allows reading of protected properties.
     * 
     * @param string $key The property name.
     * 
     * @return mixed The property value.
     * 
     */
    public function __get($key = null)
    {
        $prop = array('col', 'idx', 'name', 'paging', 'all_class', 'row_class', 'order', );
        if (in_array($key, $prop)) {
            $key = "_$key";
            return $this->$key;
        } else {
            return null;
        }
    }
    
    /**
     * 
     * Sets the number of rows per page.
     * 
     * @param int $val The number of rows per page.
     * 
     * @return void
     * 
     */
    public function setPaging($val)
    {
        $this->_paging = (int) $val;
    }
    
    /**
     * 
     * Sets the class returned by fetchRow() calls.
     * 
     * @param string $class The class name.  If empty, uses 'Solar_Sql_Row'.
     * 
     * @return void
     * 
     */
    public function setFetchRowClass($class)
    {
        if (! $class) {
            $class = 'Solar_Sql_Row';
        }
        $this->_row_class = $class;
    }
    
    /**
     * 
     * Sets the class returned by fetchAll() calls.
     * 
     * @param string $class The class name.  If empty, uses 'Solar_Sql_Rowset'.
     * 
     * @return void
     * 
     */
    public function setFetchAllClass($class)
    {
        if (! $class) {
            $class = 'Solar_Sql_Rowset';
        }
        $this->_all_class = $class;
    }
    
    /**
     * 
     * Returns a fully-qualified column name ("tablename.colname").
     * 
     * If the column does not exist in the table, returns null.
     * 
     * @param string $key The column name itself.
     * 
     * @return string The column name prefixed with the table name.
     * 
     */
    public function getColName($key)
    {
        if (! empty($this->_col[$key])) {
            return $this->_name . '.' . $key;
        }
    }
    
    /**
     * 
     * Inserts or updates a single row based on its ID.
     * 
     * @param array $data An associative array of data to be saved, in
     * the format (field => value).
     * 
     * @return array The data as inserted or updated.
     * 
     */
    public function save($data)
    {
        $this->_connect();
        if (empty($data['id'])) {
            return $this->insert($data);
        } else {
            $where = $this->_sql->quoteInto('id = ?', $data['id']);
            return $this->update($data, $where);
        }
    }
    
    /**
     * 
     * Validates and inserts data into the table.
     * 
     * @param array $data An associative array of data to be inserted, in
     * the format (field => value).
     * 
     * @return array The data as inserted.
     * 
     */
    public function insert($data)
    {
        // set defaults
        $data = array_merge($this->fetchNew()->toArray(), (array) $data);
        
        // add created/updated timestamps
        $now = date('Y-m-d\TH:i:s');
        
        if (array_key_exists($this->_col_created, $this->_col)
        && empty($data[$this->_col_created])) {
            // add created time
            $data[$this->_col_created] = $now;
        }
        
        if (array_key_exists($this->_col_updated, $this->_col)
        && empty($data[$this->_col_updated])) {
            // add created time
            $data[$this->_col_updated] = $now;
        }
        
        // validate and recast the data.
        $this->_applyFilters($data);
        
        // attempt the insert.
        $this->_connect();
        $result = $this->_sql->insert($this->_name, $data);
        
        // auto-add sequential values
        foreach ($this->_col as $colname => $colinfo) {
            // does this column autoincrement, and is no data provided?
            if ($colinfo['autoinc']) {
                $data[$colname] = $this->_sql->lastInsertId();
            }
        }
        
        // return
        return $data;
    }
    
    /**
     * 
     * Validates and updates data in the table based on a WHERE clause.
     * 
     * @param array $data An associative array of data to be updated, in
     * the format (field => value).
     * 
     * @param string $where An SQL WHERE clause limiting the updated
     * rows.
     * 
     * @return array The data as updated.
     * 
     */
    public function update($data, $where)
    {
        // retain primary key data in this array for return values
        $retain = array();
        
        // disallow the changing of primary key data
        foreach (array_keys($data) as $field) {
            if (! empty($this->_col[$field]['primary'])) {
                $retain[$field] = $data[$field];
                unset($data[$field]);
            }
        }
        
        // should we add updated time?
        if (array_key_exists($this->_col_updated, $this->_col)) {
            // forcibly set the "updated" timestamp
            $data[$this->_col_updated] = date('Y-m-d\TH:i:s');
        }
        
        // filter and validate data
        $this->_applyFilters($data);
        
        // attempt the update
        $this->_connect();
        $result = $this->_sql->update($this->_name, $data, $where);
        
        // restore retained primary key data
        return array_merge($data, $retain);
    }
    
    /**
     * 
     * Deletes rows in the table based on a WHERE clause.
     * 
     * @param string $where An SQL WHERE clause limiting the deleted rows.
     * 
     * @return void
     * 
     */
    public function delete($where)
    {
        $this->_connect();
        $result = $this->_sql->delete($this->_name, $where);
        return $result;
    }
    
    /**
     * 
     * Convenience method to select rows from this table.
     * 
     * @param string $type The type of select to execute: 'all', 'one',
     * 'value', etc. Default is 'pdo'.
     * 
     * @param string|array $where A Solar_Sql_Select::multiWhere() parameter.
     * 
     * @param string|array $order A Solar_Sql_Select::order() parameter.
     * 
     * @param int $page The page number of rows to fetch.
     * 
     * @return mixed
     * 
     */
    public function select($type = 'pdo', $where = null, $order = null,
        $page = null)
    {
        if ($type == 'rowset') {
            $class = $this->_all_class;
        } elseif ($type == 'row') {
            $class = $this->_row_class;
        } else {
            $class = null;
        }
        
        $select = $this->_newSelect();
        $result = $select->from($this->_name, array_keys($this->_col))
                         ->multiWhere($where)
                         ->order($order)
                         ->setPaging($this->_paging)
                         ->limitPage($page)
                         ->fetch($type, $class);
        
        if ($result instanceof Solar_Sql_Row) {
            $result->setSave($this);
        }
        
        return $result;
    }
    
    /**
     * 
     * Fetches one row from the table by its primary key ID.
     * 
     * @param int $id The primary key ID value.
     * 
     * @return Solar_Sql_Row
     * 
     */
    public function fetch($id)
    {
        $where = array('id = ?' => $id);
        return $this->select('row', $where);
    }
    
    /**
     * 
     * Fetches all rows by arbitrary criteria.
     * 
     * @param string|array $where A Solar_Sql_Select::multiWhere() parameter.
     * 
     * @param string|array $order A Solar_Sql_Select::order() parameter.
     * 
     * @param int $page The page number of rows to fetch.
     * 
     * @return Solar_Sql_Rowset
     * 
     */
    public function fetchAll($where = null, $order = null, $page = null)
    {
        return $this->select('rowset', $where, $order, $page);
    }
    
    /**
     * 
     * Fetches one row by arbitrary criteria.
     * 
     * @param string|array $where A Solar_Sql_Select::multiWhere() parameter.
     * 
     * @param string|array $order A Solar_Sql_Select::order() parameter.
     * 
     * @return Solar_Sql_Row
     * 
     */
    public function fetchRow($where = null, $order = null)
    {
        return $this->select('row', $where, $order);
    }
    
    /**
     * 
     * Returns a new row of column keys and default values.
     * 
     * @return Solar_Sql_Row
     * 
     */
    public function fetchNew()
    {
        // the array of default data
        $data = array();
        
        // loop through each specified column and collect default data
        $spec = array_keys($this->_col);
        foreach ($spec as $name) {
            
            // skip columns that don't exist
            if (empty($this->_col[$name])) {
                continue;
            }
            
            // get the column info
            $info = $this->_col[$name];
            
            // is there a default set?
            if (empty($info['default'])) {
                // no default, so it's null.
                $data[$name] = null;
                continue;
            }
            
            // yes, so get it based on the kind of default.
            // we shift off the front of the array as we go.
            // element 0 is the type (literal or callback),
            // element 1 is the literal (or callback name),
            // elements 2+ are any arguments for a callback.
            $type = array_shift($info['default']);
            switch ($type) {
            
            case 'callback':
                $func = array_shift($info['default']);
                $data[$name] = call_user_func_array($func, $info['default']);
                break;
            
            case 'literal':
                $data[$name] = array_shift($info['default']);
                break;
            
            default:
                $data[$name] = null;
            }
        }
        
        // done!
        $row = Solar::factory($this->_row_class, array('data' => $data));
        $row->setSave($this);
        return $row;
    }
    
    /**
     * 
     * Fetches one row by arbitrary criteria.
     * 
     * @param string|array $where A Solar_Sql_Select::multiWhere() parameter.
     * 
     * @param string|array $order A Solar_Sql_Select::order() parameter.
     * 
     * @return Solar_Sql_Row
     * 
     * @deprecated Use Solar_Sql_Table::fetchRow() instead.
     * 
     */
    public function fetchWhere($where = null, $order = null)
    {
        return $this->select('row', $where, $order);
    }
    
    // -----------------------------------------------------------------
    // 
    // Support and management methods.
    // 
    // -----------------------------------------------------------------
    
    /**
     * 
     * Gets a new Solar_Sql_Select tool, with the proper SQL object injected
     * automatically.
     * 
     * Note: this forces a database connection so that we have the SQL object
     * properly constructed.
     * 
     * @return Solar_Sql_Select
     * 
     */
    protected function _newSelect()
    {
        $this->_connect();
        return Solar::factory(
            'Solar_Sql_Select',
            array('sql' => $this->_sql)
        );
    }
    
    /**
     * 
     * Use this to set up extended table objects.
     * 
     * @return void
     * 
     */
    protected function _setup()
    {
    }
    
    /**
     * 
     * Fixes the $col and $idx properties after user setup.
     * 
     * @return void
     * 
     */
    protected function _autoSetup()
    {
        // make sure there's a table name.  defaults to the
        // part after the last underscore, then converts camelCaps 
        // to underscore_words.
        if (empty($this->_name)) {
            
            // get the class name
            $tmp = get_class($this);
            
            // get the part after the last underscore
            $pos = strrpos($tmp, '_');
            if ($pos !== false) {
                $tmp = substr($tmp, $pos + 1);
            }
            
            // camels to unders
            $this->_name = preg_replace('/([a-z])([A-Z])/', "$1_$2", $tmp);
        }
        
        // make sure table name is lower case regardless
        $this->_name = strtolower($this->_name);
        
        // a baseline column definition
        $basecol = array(
            'name'    => null,
            'type'    => null,
            'size'    => null,
            'scope'   => null,
            'primary' => false,
            'require' => false,
            'autoinc' => false,
            'default' => null,
            'filter'   => array(),
        );
        
        // baseline index definition
        $baseidx = array(
            'name'    => null,
            'type'    => 'normal',
            'cols'    => null,
        );
        
        // auto-added columns and indexes
        $autocol = array();
        $autoidx = array();
        
        // auto-add an ID column and index for unique identification
        if (! array_key_exists('id', $this->_col)) {
            $autocol['id'] = array(
                'type'    => 'int',
                'primary' => true,
                'require' => true,
                'autoinc' => true,
            );
            
            $autoidx['id'] = array(
                'type' => 'unique',
                'cols' => 'id',
            );
        }
        
        // merge the auto-added items on top of the rest
        $this->_col = array_merge($autocol, $this->_col);
        $this->_idx = array_merge($autoidx, $this->_idx);
        
        // fix up each column to have a full set of info
        foreach ($this->_col as $name => $info) {
        
            // fill in missing elements
            $info = array_merge($basecol, $info);
            
            // make sure there's a name
            $info['name'] = $name;
            
            // if 'filter' is a string, make the validation a simple
            // Solar_DataFilter method call.
            if (is_string($info['filter'])) {
                $info['filter'] = array($info['filter']);
            }
            
            // if 'default' is not already an array, make it
            // one as a literal.  this lets you avoid the array
            // when setting up simple literals.
            if (! is_array($info['default'])) {
                $info['default'] = array('literal', $info['default']);
            }
            
            // save back into the column info
            $this->_col[$name] = $info;
        }
        
        // fix filters and add final fallbacks
        $this->_fixFilters();
        
        // fix up each index to have a full set of info
        foreach ($this->_idx as $key => $val) {
            
            if (is_int($key) && is_string($val)) {
                // array('col')
                $info = array(
                    'name' => $val,
                    'type' => 'normal',
                    'cols' => array($val),
                );
            } elseif (is_string($key) && is_string($val)) {
                // array('col' => 'unique')
                $info = array(
                    'name' => $key,
                    'type' => $val,
                    'cols' => array($key),
                );
            } else {
                // array('alt' => array('type' => 'normal', 'cols' => array(...)))
                $info = array_merge($baseidx, (array) $val);
                $info['name'] = (string) $key;
                settype($info['cols'], 'array');
            }
            
            $this->_idx[$key] = $info;
        }
    }
    
    /**
     * 
     * Creates the table in the database if it does not already exist.
     * 
     * @return bool False if the table already existed and didn't
     * need to be created, or true if the table did not exist and was
     * successfully created.
     * 
     */
    protected function _autoCreate()
    {
        // is a table with the same name already there?
        $tmp = $this->_sql->fetchTableList();
        $here = strtolower($this->_name);
        foreach ($tmp as $there) {
            if ($here == strtolower($there)) {
                // table already exists
                return false;
            }
        }
        
        // create the table itself
        $this->_sql->createTable(
            $this->_name,
            $this->_col
        );
        
        // create each of the indexes
        foreach ($this->_idx as $name => $info) {
            try {
                // create this index
                $this->_sql->createIndex(
                    $this->_name,
                    $info['name'],
                    $info['type'] == 'unique',
                    $info['cols']
                );
            } catch (Exception $e) {
                /** @todo Does this throw a TableNotCreated exception too? */
                // cancel the whole deal.
                $this->_sql->dropTable($this->_name);
                throw $e;
            }
        }
        
        // post-creation
        try {
            $this->_postCreate();
        } catch (Exception $e) {
            /** @todo Does this throw a TableNotCreated exception too? */
            // cancel the whole deal.
            $this->_sql->dropTable($this->_name);
            throw $e;
        }
        
        // creation of the table and its indexes is complete
        return true;
    }
    
    /**
     * 
     * Additional table-creation tasks, such as inserting rows.
     * 
     * @return void
     * 
     */
    protected function _postCreate()
    {
    }
    
    /**
     * 
     * Fixes all filters
     * 
     * @return void
     * 
     */
    protected function _fixFilters()
    {
        // make sure that strings are converted
        // to arrays so that _applyFilters() works properly.
        foreach ($this->_col as $col => $info) {
            foreach ($info['filter'] as $key => $val) {
                if (is_string($val)) {
                    $this->_col[$col]['filter'][$key] = array($val);
                }
            }
        }
        
        // low and high range values for integer filters
        $range = array(
            'smallint' => array(pow(-2, 15), pow(+2, 15) - 1),
            'int'      => array(pow(-2, 31), pow(+2, 31) - 1),
            'bigint'   => array(pow(-2, 63), pow(+2, 63) - 1)
        );
        
        // columns names
        $cols = array_keys($this->_col);
        
        // add final fallback filters based on data type
        foreach ($cols as $field) {
            
            // column type
            $type = $this->_col[$field]['type'];
            $info = $this->_col[$field];
            
            switch ($type) {
            case 'bool':
                $this->_col[$field]['filter'][] = array('validateBool');
                $this->_col[$field]['filter'][] = array('sanitizeBool');
                break;
                
            case 'char':
            case 'varchar':
                $this->_col[$field]['filter'][] = array('validateString');
                $this->_col[$field]['filter'][] = array('validateMaxLength',
                    $info['size']);
                $this->_col[$field]['filter'][] = array('sanitizeString');
                break;
                
            case 'smallint':
            case 'int':
            case 'bigint':
                $this->_col[$field]['filter'][] = array('validateInt');
                $this->_col[$field]['filter'][] = array('validateRange',
                    $range[$type][0], $range[$type][1]);
                $this->_col[$field]['filter'][] = array('sanitizeInt');
                break;
                
            case 'numeric':
                $this->_col[$field]['filter'][] = array('validateNumeric');
                $this->_col[$field]['filter'][] = array('validateSizeScope',
                    $info['size'], $info['scope']);
                $this->_col[$field]['filter'][] = array('sanitizeNumeric');
                break;
                
            case 'float':
                $this->_col[$field]['filter'][] = array('validateFloat');
                $this->_col[$field]['filter'][] = array('sanitizeFloat');
                break;
                
            case 'clob':
                $this->_col[$field]['filter'][] = array('validateString');
                $this->_col[$field]['filter'][] = array('sanitizeString');
                break;
                
            case 'date':
                $this->_col[$field]['filter'][] = array('validateIsoDate');
                $this->_col[$field]['filter'][] = array('sanitizeIsoDate');
                break;
                
            case 'time':
                $this->_col[$field]['filter'][] = array('validateIsoTime');
                $this->_col[$field]['filter'][] = array('sanitizeIsoTime');
                break;
                
            case 'timestamp':
                $this->_col[$field]['filter'][] = array('validateIsoTimestamp');
                $this->_col[$field]['filter'][] = array('sanitizeIsoTimestamp');
                break;
            }
        }
    }
    
    /**
     * 
     * Applies sanitize/validate filters for all data
     * 
     * @param array &data Data as col => val passed by reference
     * 
     * @return void
     * 
     * @throws Solar_Exception if something invalidated
     * 
     */
    protected function _applyFilters(&$data)
    {
        $invalid = array();
        
        foreach ($data as $field => &$val) {
            
            // does this col exist?
            if (! array_key_exists($field, $this->_col)) {
                // ...no, remove it!
                unset($data[$field]);
                continue;
            }
            
            foreach ($this->_col[$field]['filter'] as $params) {
                
                // take the method name off the top of the params ...
                $method = array_shift($params);
                
                // ... and put the value in its place.
                array_unshift($params, $data[$field]);
                
                if ($this->_col[$field]['autoinc']) {
                    $this->_obj_filter->setRequire(false);
                } else {
                    $this->_obj_filter->setRequire($this->_col[$field]['require']);
                }
                
                // call the filtering method
                $result = call_user_func_array(
                    array($this->_obj_filter, $method),
                    $params
                );
                
                // make sure everything is required by default
                $this->_obj_filter->setRequire(true);
                
                // did the filter sanitize, or did it validate?
                $type = strtolower(substr($method, 0, 8));
                
                // what to do with the result?
                if ($type == 'sanitize') {
                    // retain the sanitized value
                    $val = $result;
                } elseif ($type == 'validate' && $result === false) {
                    // a validation method failed; use the method name as
                    // the locale translation key, converting from camelCase
                    // to camel_Case, then to CAMEL_CASE.
                    $tmp = preg_replace('/([a-z])([A-Z])/', '$1_$2', $method);
                    $tmp = strtoupper($tmp);
                    $invalid[$field] = $this->locale($tmp);
                    // no more validations on this key
                    break;
                }
            }
        }
        
        // were there any errors?
        if (! empty($invalid)) {
            throw $this->_exception(
                'ERR_INVALID_DATA',
                array('info' => $invalid)
            );
        }
    }
    
    protected function _autoAddValid()
    {
        /**
        $autovalids = array(
            `bool`      => array()
            `char`      => array()
            `varchar`   =>
            `smallint`  => size
            `int`       => size
            `bigint`    => size
            `numeric`   =>
            `float`     =>
            `clob`      => array('')
            `date`      => array('isoDate')
            `time`      => array('isoTime')
            `timestamp` => array('isoDateTime')
        );
        */
        // low and high range values for integers
        $int_range = array(
            'smallint' => array(pow(-2, 15), pow(+2, 15) - 1),
            'int'      => array(pow(-2, 31), pow(+2, 31) - 1),
            'bigint'   => array(pow(-2, 63), pow(+2, 63) - 1)
        );
        
        foreach ($this->_col as $name => &$col) {
            
            switch ($col['type']) {
                
            case 'bool':
                break;

            case 'char':
            case 'varchar':
                // are there any validations?
                if (empty($col['valid'])) {
                    
                    if($col['require']) {
                        $blank = Solar_Valid::NOT_BLANK;
                    } else {
                        $blank = SOlar_Valid::OR_BLANK;
                    }

                    // add maxLength rule
                    $col['valid'] = array(
                        'maxLength',
                        'VALIDATE_MAXLENGTH',
                        $col['size'],
                        $blank
                    );
                }
                break;
                
            case 'int':
            case 'bigint':
            case 'smallint':
                if (empty($col['valid'])) {
                    $min = $int_range[$col['type']][0];
                    $max = $int_range[$col['type']][1];
                    
                    $col['valid'] = array('range', 'VALIDATE_RANGE', $min, $max);
                }
                break;
            
            case 'float':
                break;

            case 'numeric':
            
            case 'date':
            
            case 'time':
            
            case 'timestamp':
                break;
            }
        }
    }
    /*
     // are there any validations?
     if (! empty($col['valid'])) {

         // is first a 'multiple' rule?
         if ($col['valid'][0][0] == 'multiple') {

             // yes, so go through them and search
             // for maxLenght validation
             $found = false;
             foreach ($col['valid'][0][2] as $valid) {
                 // is this it?
                 if ($valid[0] == 'maxLenght') {
                     $found = true;
                     break;
                 }
             }
             
             if (! $found) {
                 if ($col['require']) {
                     // add a rangeLenght rule because the col
                     // is required
                     $col['valid'][0][2][] =
                         array('maxLenght', 1, $col['size']);
                     
                 } else {
                     // add maxLenght rule only
                     $col['valid'][0][2][] =
                         array('maxLenght', $col['size']);
                 }
             }
         } else {
             
             // check if this is a rangeLenght rule
             
             
             if ($col['valid'][0][0]) {
                 # code...
             }
         }
     } else {
         
         // it's empty so add rangeLenght rule
     }
     
     */
}
